package kwdb

import (
	"fmt"
	"strings"
	"time"

	"github.com/timescale/tsbs/cmd/tsbs_generate_queries/uses/devops"
	"github.com/timescale/tsbs/pkg/query"
)

// TODO: Remove the need for this by continuing to bubble up errors
func panicIfErr(err error) {
	if err != nil {
		panic(err.Error())
	}
}

// Devops produces TimescaleDB-specific queries for all the devops query types.
type Devops struct {
	*BaseGenerator
	*devops.Core
}

// getHostWhereWithHostnames creates WHERE SQL statement for multiple hostnames.
// NOTE 'WHERE' itself is not included, just hostname filter clauses, ready to concatenate to 'WHERE' string
func (d *Devops) getHostWhereWithHostnames(hostnames []string) string {
	var hostnameClauses []string
	for _, s := range hostnames {
		hostnameClauses = append(hostnameClauses, fmt.Sprintf("'%s'", s))
	}
	return fmt.Sprintf("satisfying device.hostname in (%s)", strings.Join(hostnameClauses, ","))
}

// getHostWhereString gets multiple random hostnames and creates a WHERE SQL statement for these hostnames.
func (d *Devops) getHostWhereString(nHosts int) string {
	hostnames, err := d.GetRandomHosts(nHosts)
	panicIfErr(err)
	return d.getHostWhereWithHostnames(hostnames)
}

func (d *Devops) getSelectClausesAggMetrics(agg string, metrics []string) []string {
	selectClauses := make([]string, len(metrics))
	for i, m := range metrics {
		selectClauses[i] = fmt.Sprintf("%s(%s)", agg, m)
	}

	return selectClauses
}

func (d *Devops) GroupByTime(qi query.Query, nHosts, numMetrics int, timeRange time.Duration) {
	interval := d.Interval.MustRandWindow(timeRange)
	metrics, err := devops.GetCPUMetricsSlice(numMetrics)
	panicIfErr(err)
	selectClauses := d.getSelectClausesAggMetrics("max", metrics)
	if len(selectClauses) < 1 {
		panic(fmt.Sprintf("invalid number of select clauses: got %d", len(selectClauses)))
	}
	hostnames, err := d.GetRandomHosts(nHosts)
	if nil != err {
		panic(fmt.Sprintf("get randam host error %s", err.Error()))
	} else if len(hostnames) < 1 {
		panic(fmt.Sprintf("invalid number of host: got %d", len(hostnames)))
	}

	sql := ""
	var prepare bool
	if kaiwudb, ok := qi.(*query.Kwdb); ok {
		prepare = kaiwudb.GetPrepare()
	}
	if nHosts == 1 {
		panicIfErr(err)
		if !prepare {
			sql = fmt.Sprintf(`SELECT time_bucket(k_timestamp, '60s') as k_timestamp, %s FROM %s.cpu WHERE hostname='%s' AND k_timestamp >= '%s' AND k_timestamp < '%s' GROUP BY time_bucket(k_timestamp, '60s') ORDER BY time_bucket(k_timestamp, '60s')`,
				strings.Join(selectClauses, ", "),
				d.CPUDBName,
				hostnames[0],
				parseTime(time.UnixMilli(interval.StartUnixMillis()).UTC()),
				parseTime(time.UnixMilli(interval.EndUnixMillis()).UTC()))
		} else {
			sql = fmt.Sprintf(`%s,%d,%d`,
				hostnames[0],
				interval.StartUnixMillis(),
				interval.EndUnixMillis())
		}
	} else {
		if !prepare {
			sql = fmt.Sprintf(`SELECT time_bucket(k_timestamp, '60s') as k_timestamp, %s FROM %s.cpu WHERE hostname IN (%s) AND k_timestamp >= '%s' AND k_timestamp < '%s' GROUP BY time_bucket(k_timestamp,'60s') ORDER BY time_bucket(k_timestamp,'60s')`,
				strings.Join(selectClauses, ", "),
				d.CPUDBName,
				"'"+strings.Join(hostnames, "', '")+"'",
				parseTime(time.UnixMilli(interval.StartUnixMillis()).UTC()),
				parseTime(time.UnixMilli(interval.EndUnixMillis()).UTC()))
		} else {
			sql = fmt.Sprintf(`%s,%d,%d`,
				strings.Join(hostnames, ","),
				interval.StartUnixMillis(),
				interval.EndUnixMillis())
		}
	}

	humanLabel := fmt.Sprintf("KWDB %d cpu metric(s), random %4d hosts, random %s by 1m", numMetrics, nHosts, timeRange)
	humanDesc := fmt.Sprintf("%s: %s", humanLabel, interval.StartString())
	d.fillInQuery(qi, humanLabel, humanDesc, devops.TableName, sql)
}

func (d *Devops) GroupByOrderByLimit(qi query.Query) {
	interval := d.Interval.MustRandWindow(time.Hour)
	var prepare bool
	if kaiwudb, ok := qi.(*query.Kwdb); ok {
		prepare = kaiwudb.GetPrepare()
	}
	var sql string
	if !prepare {
		sql = fmt.Sprintf(`SELECT time_bucket(k_timestamp, '60s') as k_timestamp, max(usage_user) FROM %s.cpu WHERE k_timestamp < '%s' GROUP BY time_bucket(k_timestamp, '60s') ORDER BY time_bucket(k_timestamp, '60s') LIMIT 5`,
			d.CPUDBName,
			parseTime(time.UnixMilli(interval.EndUnixMillis()).UTC()))
	} else {
		sql = fmt.Sprintf(`%d`,
			interval.EndUnixMillis())
	}

	humanLabel := "KWDB max cpu over last 5 min-intervals (random end)"
	humanDesc := fmt.Sprintf("%s: %s", humanLabel, interval.EndString())
	d.fillInQuery(qi, humanLabel, humanDesc, devops.TableName, sql)
}

// GroupByTimeAndPrimaryTag selects the AVG of numMetrics metrics under 'cpu' per device per hour for a day,
func (d *Devops) GroupByTimeAndPrimaryTag(qi query.Query, numMetrics int) {
	metrics, err := devops.GetCPUMetricsSlice(numMetrics)
	panicIfErr(err)
	interval := d.Interval.MustRandWindow(devops.DoubleGroupByDuration)
	var prepare bool
	if kaiwudb, ok := qi.(*query.Kwdb); ok {
		prepare = kaiwudb.GetPrepare()
	}
	selectClauses := d.getSelectClausesAggMetrics("avg", metrics)
	var sql string
	if !prepare {
		sql = fmt.Sprintf(`SELECT time_bucket(k_timestamp, '3600s') as k_timestamp, hostname, %s FROM %s.cpu WHERE k_timestamp >= '%s' AND k_timestamp < '%s' GROUP BY hostname, time_bucket(k_timestamp, '3600s') ORDER BY hostname, time_bucket(k_timestamp, '3600s')`,
			strings.Join(selectClauses, ", "),
			d.CPUDBName,
			parseTime(time.UnixMilli(interval.StartUnixMillis()).UTC()),
			parseTime(time.UnixMilli(interval.EndUnixMillis()).UTC()))
	} else {
		sql = fmt.Sprintf(`%d,%d`,
			interval.StartUnixMillis(),
			interval.EndUnixMillis())
	}

	humanLabel := devops.GetDoubleGroupByLabel("KWDB", numMetrics)
	humanDesc := fmt.Sprintf("%s: %s", humanLabel, interval.StartString())
	d.fillInQuery(qi, humanLabel, humanDesc, devops.TableName, sql)
}

func (d *Devops) MaxAllCPU(qi query.Query, nHosts int, duration time.Duration) {
	interval := d.Interval.MustRandWindow(duration)

	metrics := devops.GetAllCPUMetrics()
	selectClauses := d.getSelectClausesAggMetrics("max", metrics)
	if len(selectClauses) < 1 {
		panic(fmt.Sprintf("invalid number of select clauses: got %d", len(selectClauses)))
	}
	hostnames, err := d.GetRandomHosts(nHosts)
	if nil != err {
		panic(fmt.Sprintf("get randam host error %s", err.Error()))
	} else if len(hostnames) < 1 {
		panic(fmt.Sprintf("invalid number of host: got %d", len(hostnames)))
	}
	sql := ""
	var prepare bool
	if kaiwudb, ok := qi.(*query.Kwdb); ok {
		prepare = kaiwudb.GetPrepare()
	}
	if nHosts == 1 {
		if !prepare {
			sql = fmt.Sprintf(`SELECT time_bucket(k_timestamp, '3600s') as k_timestamp, %s FROM %s.cpu WHERE hostname = '%s' AND k_timestamp >= '%s' AND k_timestamp < '%s' GROUP BY time_bucket(k_timestamp, '3600s') ORDER BY time_bucket(k_timestamp, '3600s')`,
				strings.Join(selectClauses, ", "),
				d.CPUDBName,
				hostnames[0],
				parseTime(time.UnixMilli(interval.StartUnixMillis()).UTC()),
				parseTime(time.UnixMilli(interval.EndUnixMillis()).UTC()))
		} else {
			sql = fmt.Sprintf(`%s,%d,%d`,
				hostnames[0],
				interval.StartUnixMillis(),
				interval.EndUnixMillis())
		}
	} else {
		if !prepare {
			sql = fmt.Sprintf(`SELECT time_bucket(k_timestamp, '3600s') as k_timestamp, %s FROM %s.cpu WHERE hostname IN (%s) AND k_timestamp >= '%s' AND k_timestamp < '%s' GROUP BY time_bucket(k_timestamp, '3600s') ORDER BY time_bucket(k_timestamp, '3600s')`,
				strings.Join(selectClauses, ", "),
				d.CPUDBName,
				"'"+strings.Join(hostnames, "', '")+"'",
				parseTime(time.UnixMilli(interval.StartUnixMillis()).UTC()),
				parseTime(time.UnixMilli(interval.EndUnixMillis()).UTC()))
		} else {
			sql = fmt.Sprintf(`%s,%d,%d`,
				strings.Join(hostnames, ","),
				interval.StartUnixMillis(),
				interval.EndUnixMillis())
		}
	}
	humanLabel := devops.GetMaxAllLabel("KWDB", nHosts)
	humanDesc := fmt.Sprintf("%s: %s", humanLabel, interval.StartString())
	d.fillInQuery(qi, humanLabel, humanDesc, devops.TableName, sql)
}

func (d *Devops) LastPointPerHost(qi query.Query) {
	metrics, err := devops.GetCPUMetricsSlice(devops.GetCPUMetricsLen())
	panicIfErr(err)
	selectClauses := d.getSelectClausesAggMetrics("last_row", metrics)
	if len(selectClauses) != devops.GetCPUMetricsLen() {
		panic(fmt.Sprintf("invalid number of select clauses: got %d", len(selectClauses)))
	}
	var prepare bool
	if kaiwudb, ok := qi.(*query.Kwdb); ok {
		prepare = kaiwudb.GetPrepare()
	}
	var sql string
	if !prepare {
		sql = fmt.Sprintf(`SELECT last_row(k_timestamp), %s, hostname FROM %s.cpu GROUP BY hostname`,
			strings.Join(selectClauses, ", "),
			d.CPUDBName)
	} else {
		sql = fmt.Sprintf(``)
	}

	humanLabel := "KWDB last row per host"
	humanDesc := humanLabel
	d.fillInQuery(qi, humanLabel, humanDesc, devops.TableName, sql)
}

func (d *Devops) HighCPUForHosts(qi query.Query, nHosts int) {
	interval := d.Interval.MustRandWindow(devops.HighCPUDuration)
	//var hostWhereClause string
	//if nHosts == 0 {
	//	hostWhereClause = ""
	//} else {
	//	hostWhereClause = fmt.Sprintf("AND %s", d.getHostWhereString(nHosts))
	//}
	var prepare bool
	if kaiwudb, ok := qi.(*query.Kwdb); ok {
		prepare = kaiwudb.GetPrepare()
	}

	sql := ""
	if nHosts == 1 {
		hostnames, err := d.GetRandomHosts(nHosts)
		panicIfErr(err)
		if !prepare {
			sql = fmt.Sprintf(`SELECT k_timestamp,usage_user,usage_system,usage_idle,usage_nice,usage_iowait,usage_irq,usage_softirq,usage_steal,usage_guest,usage_guest_nice FROM %s.cpu WHERE hostname='%s' AND usage_user > 90.0 AND k_timestamp >= '%s' AND k_timestamp < '%s'`,
				d.CPUDBName,
				d.CPUDBName,
				hostnames[0],
				parseTime(time.UnixMilli(interval.StartUnixMillis()).UTC()),
				parseTime(time.UnixMilli(interval.EndUnixMillis()).UTC()))
		} else {
			sql = fmt.Sprintf(`%s,%d,%d`,
				hostnames[0],
				interval.StartUnixMillis(),
				interval.EndUnixMillis())
		}
	} else {
		if !prepare {
			sql = fmt.Sprintf(`SELECT k_timestamp,usage_user,usage_system,usage_idle,usage_nice,usage_iowait,usage_irq,usage_softirq,usage_steal,usage_guest,usage_guest_nice FROM %s.cpu WHERE usage_user > 90.0 AND k_timestamp >= '%s' AND k_timestamp < '%s'`,
				d.CPUDBName,
				parseTime(time.UnixMilli(interval.StartUnixMillis()).UTC()),
				parseTime(time.UnixMilli(interval.EndUnixMillis()).UTC()))
		} else {
			sql = fmt.Sprintf(`%d,%d`,
				interval.StartUnixMillis(),
				interval.EndUnixMillis())
		}
	}
	humanLabel, err := devops.GetHighCPULabel("KWDB", nHosts)
	panicIfErr(err)
	humanDesc := fmt.Sprintf("%s: %s", humanLabel, interval.StartString())
	d.fillInQuery(qi, humanLabel, humanDesc, devops.TableName, sql)
}
